package com.yboot.starter.springdoc.core;

import cn.hutool.core.io.IoUtil;
import io.swagger.v3.core.jackson.TypeNameResolver;
import io.swagger.v3.core.util.AnnotationsUtils;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tags;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.Paths;
import io.swagger.v3.oas.models.tags.Tag;
import java.io.StringReader;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springdoc.core.OpenAPIService;
import org.springdoc.core.PropertyResolverUtils;
import org.springdoc.core.SecurityService;
import org.springdoc.core.SpringDocConfigProperties;
import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
import org.springdoc.core.providers.JavadocProvider;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.web.method.HandlerMethod;

public class OpenApiHandler extends OpenAPIService {
    private static final Logger log = LoggerFactory.getLogger(OpenApiHandler.class);
    private static Class<?> basicErrorController;
    private final SecurityService securityParser;
    private final Map<String, Object> mappingsMap = new HashMap();
    private final Map<HandlerMethod, Tag> springdocTags = new HashMap();
    private final Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomisers;
    private final Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomizers;
    private final SpringDocConfigProperties springDocConfigProperties;
    private final Map<String, OpenAPI> cachedOpenAPI = new HashMap();
    private final PropertyResolverUtils propertyResolverUtils;
    private final Optional<JavadocProvider> javadocProvider;
    private ApplicationContext context;
    private OpenAPI openAPI;
    private boolean isServersPresent;
    private String serverBaseUrl;

    public OpenApiHandler(Optional<OpenAPI> openAPI, SecurityService securityParser, SpringDocConfigProperties springDocConfigProperties, PropertyResolverUtils propertyResolverUtils, Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomizers, Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomizers, Optional<JavadocProvider> javadocProvider) {
        super(openAPI, securityParser, springDocConfigProperties, propertyResolverUtils, openApiBuilderCustomizers, serverBaseUrlCustomizers, javadocProvider);
        if (openAPI.isPresent()) {
            this.openAPI = (OpenAPI)openAPI.get();
            if (this.openAPI.getComponents() == null) {
                this.openAPI.setComponents(new Components());
            }

            if (this.openAPI.getPaths() == null) {
                this.openAPI.setPaths(new Paths());
            }

            if (!CollectionUtils.isEmpty(this.openAPI.getServers())) {
                this.isServersPresent = true;
            }
        }

        this.propertyResolverUtils = propertyResolverUtils;
        this.securityParser = securityParser;
        this.springDocConfigProperties = springDocConfigProperties;
        this.openApiBuilderCustomisers = openApiBuilderCustomizers;
        this.serverBaseUrlCustomizers = serverBaseUrlCustomizers;
        this.javadocProvider = javadocProvider;
        if (springDocConfigProperties.isUseFqn()) {
            TypeNameResolver.std.setUseFqn(true);
        }

    }

    public Operation buildTags(HandlerMethod handlerMethod, Operation operation, OpenAPI openAPI, Locale locale) {
        Set<Tag> tags = new HashSet();
        Set<String> tagsStr = new HashSet();
        this.buildTagsFromMethod(handlerMethod.getMethod(), tags, tagsStr, locale);
        this.buildTagsFromClass(handlerMethod.getBeanType(), tags, tagsStr, locale);
        if (!CollectionUtils.isEmpty(tagsStr)) {
            tagsStr = tagsStr.stream().map((str) -> {
                return this.propertyResolverUtils.resolve(str, locale);
            }).collect(Collectors.toSet());
        }

        if (this.springdocTags.containsKey(handlerMethod)) {
            Tag tag = this.springdocTags.get(handlerMethod);
            tagsStr.add(tag.getName());
            if (openAPI.getTags() == null || !openAPI.getTags().contains(tag)) {
                openAPI.addTagsItem(tag);
            }
        }

        if (!CollectionUtils.isEmpty(tagsStr)) {
            if (CollectionUtils.isEmpty(operation.getTags())) {
                operation.setTags(new ArrayList(tagsStr));
            } else {
                Set<String> operationTagsSet = new HashSet(operation.getTags());
                operationTagsSet.addAll(tagsStr);
                operation.getTags().clear();
                operation.getTags().addAll(operationTagsSet);
            }
        }

        if (this.isAutoTagClasses(operation)) {
            String description;
            if (this.javadocProvider.isPresent()) {
                description = this.javadocProvider.get().getClassJavadoc(handlerMethod.getBeanType());
                if (StringUtils.isNotBlank(description)) {
                    Tag tag = new Tag();
                    // 自定义部分 修改使用java注释当tag名
                    List<String> list = (List) IoUtil.readLines(new StringReader(description), new ArrayList());
                    tag.setName((String)list.get(0));
                    operation.addTagsItem((String)list.get(0));
                    tag.setDescription(description);
                    if (openAPI.getTags() == null || !openAPI.getTags().contains(tag)) {
                        openAPI.addTagsItem(tag);
                    }
                }
            } else {
                description = splitCamelCase(handlerMethod.getBeanType().getSimpleName());
                operation.addTagsItem(description);
            }
        }

        if (!CollectionUtils.isEmpty(tags)) {
            List<Tag> openApiTags = openAPI.getTags();
            if (!CollectionUtils.isEmpty(openApiTags)) {
                tags.addAll(openApiTags);
            }

            openAPI.setTags(new ArrayList(tags));
        }

        SecurityRequirement[] securityRequirements = this.securityParser.getSecurityRequirements(handlerMethod);
        if (securityRequirements != null) {
            if (securityRequirements.length == 0) {
                operation.setSecurity(Collections.emptyList());
            } else {
                this.securityParser.buildSecurityRequirement(securityRequirements, operation);
            }
        }

        return operation;
    }

    private void buildTagsFromMethod(Method method, Set<Tag> tags, Set<String> tagsStr, Locale locale) {
        Set<Tags> tagsSet = AnnotatedElementUtils.findAllMergedAnnotations(method, Tags.class);
        Set<io.swagger.v3.oas.annotations.tags.Tag> methodTags = (Set)tagsSet.stream().flatMap((x) -> {
            return Stream.of(x.value());
        }).collect(Collectors.toSet());
        methodTags.addAll(AnnotatedElementUtils.findAllMergedAnnotations(method, io.swagger.v3.oas.annotations.tags.Tag.class));
        if (!CollectionUtils.isEmpty(methodTags)) {
            tagsStr.addAll(methodTags.stream().map((tag) -> {
                return this.propertyResolverUtils.resolve(tag.name(), locale);
            }).collect(Collectors.toSet()));
            List<io.swagger.v3.oas.annotations.tags.Tag> allTags = new ArrayList(methodTags);
            this.addTags(allTags, tags, locale);
        }

    }

    private void addTags(List<io.swagger.v3.oas.annotations.tags.Tag> sourceTags, Set<Tag> tags, Locale locale) {
        Optional<Set<Tag>> optionalTagSet = AnnotationsUtils.getTags((io.swagger.v3.oas.annotations.tags.Tag[])sourceTags.toArray(new io.swagger.v3.oas.annotations.tags.Tag[0]), true);
        optionalTagSet.ifPresent((tagsSet) -> {
            tagsSet.forEach((tag) -> {
                tag.name(this.propertyResolverUtils.resolve(tag.getName(), locale));
                tag.description(this.propertyResolverUtils.resolve(tag.getDescription(), locale));
                if (tags.stream().noneMatch((t) -> {
                    return t.getName().equals(tag.getName());
                })) {
                    tags.add(tag);
                }

            });
        });
    }
}
